use borsh::{
    BorshDeserialize,
    BorshSerialize,
};
use serde::{
    Deserialize,
    Serialize,
};
use solana_program::{
    program_error::ProgramError,
    program_pack::{
        IsInitialized,
        Pack,
    },
    pubkey::Pubkey,
};
use solitaire::{
    pack_type,
    processors::seeded::{
        AccountOwner,
        Owned,
        SingleOwned,
    },
};
use spl_token::state::{
    Account,
    Mint,
};

pub type Address = [u8; 32];
pub type ChainID = u16;

#[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize, Serialize, Deserialize)]
pub struct Config {
    pub wormhole_bridge: Pubkey,
}

#[cfg(not(feature = "cpi"))]
impl Owned for Config {
    fn owner(&self) -> AccountOwner {
        AccountOwner::This
    }
}

#[cfg(feature = "cpi")]
impl Owned for Config {
    fn owner(&self) -> AccountOwner {
        use std::str::FromStr;
        AccountOwner::Other(Pubkey::from_str(env!("TOKEN_BRIDGE_ADDRESS")).unwrap())
    }
}

#[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize, Serialize, Deserialize)]
pub struct EndpointRegistration {
    pub chain: ChainID,
    pub contract: Address,
}

#[cfg(not(feature = "cpi"))]
impl Owned for EndpointRegistration {
    fn owner(&self) -> AccountOwner {
        AccountOwner::This
    }
}

impl SingleOwned for EndpointRegistration {
}

#[cfg(feature = "cpi")]
impl Owned for EndpointRegistration {
    fn owner(&self) -> AccountOwner {
        use std::str::FromStr;
        AccountOwner::Other(Pubkey::from_str(env!("TOKEN_BRIDGE_ADDRESS")).unwrap())
    }
}

#[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize, Serialize, Deserialize)]
pub struct WrappedMeta {
    pub chain: ChainID,
    pub token_address: Address,
    pub original_decimals: u8,
}

impl SingleOwned for WrappedMeta {
}

#[cfg(not(feature = "cpi"))]
impl Owned for WrappedMeta {
    fn owner(&self) -> AccountOwner {
        AccountOwner::This
    }
}

#[cfg(feature = "cpi")]
impl Owned for WrappedMeta {
    fn owner(&self) -> AccountOwner {
        use std::str::FromStr;
        AccountOwner::Other(Pubkey::from_str(env!("TOKEN_BRIDGE_ADDRESS")).unwrap())
    }
}

pub mod spl_token_2022 {
    use solana_program::pubkey::Pubkey;
    use std::str::FromStr;

    pub fn id() -> Pubkey {
        Pubkey::from_str("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb").unwrap()
    }
}

trait MintExtensionPack: Pack {
    fn unpack(input: &[u8]) -> Result<Self, ProgramError>;
}

// from: https://github.com/solana-program/token-2022/blob/9cbf7a1e9bab57aabb71b4b84fc84e3670108573/interface/src/extension/mod.rs#L262-L268
// Since there is no account discriminator in these accounts, it's possible to confuse a multisig account for a mint account.
// This check prevents that by ensuring the length is not equal to a multisig account length.
// Mint accounts that happen to be 355 bytes long are out of luck (but this won't concern us, if it's even possible).
fn check_min_len_and_not_multisig(input: &[u8], minimum_len: usize) -> Result<(), ProgramError> {
    const MULTISIG_LEN: usize = 355; // spl_token::state::Multisig::LEN;
    if input.len() == MULTISIG_LEN || input.len() < minimum_len {
        Err(ProgramError::InvalidAccountData)
    } else {
        Ok(())
    }
}

impl MintExtensionPack for Mint {
    // this implementation is almost identical to the default Pack::unpack,
    // except for the length check. Instead of requiring exact length, we require
    // a minimum length, to allow for extensions.
    fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
        check_min_len_and_not_multisig(input, Self::LEN)?;
        let value: Mint = solana_program::program_pack::Pack::unpack_from_slice(input)?;
        if value.is_initialized() {
            Ok(value)
        } else {
            Err(ProgramError::UninitializedAccount)
        }
    }
}

pack_type!(
    SplMint,
    Mint,
    AccountOwner::OneOf(vec![spl_token::id(), spl_token_2022::id()]),
    MintExtensionPack
);
pack_type!(
    SplAccount,
    Account,
    AccountOwner::OneOf(vec![spl_token::id(), spl_token_2022::id()])
);

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_mint_unpack_from_slice_old_token() {
        let src: [u8; 82] = [
            0x01, 0x00, 0x00, 0x00, 0x64, 0xf1, 0x33, 0x5f, 0xe8, 0x35, 0x98, 0x99, 0x99, 0xfb,
            0xd2, 0x84, 0x35, 0xc9, 0x0b, 0x89, 0x47, 0xfb, 0x25, 0x8f, 0x7a, 0xea, 0xcb, 0x19,
            0xc8, 0x8f, 0x9b, 0x09, 0x7a, 0xe2, 0xc7, 0xe7, 0x00, 0xc0, 0x57, 0x73, 0xa5, 0x7c,
            0x02, 0x00, 0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        ];
        let mint = <Mint as MintExtensionPack>::unpack(&src).unwrap();
        assert!(mint.is_initialized);
    }

    #[test]
    fn test_mint_unpack_from_slice_new_token() {
        let src: [u8; 344] = [
            0x01, 0x00, 0x00, 0x00, 0x67, 0x94, 0x7e, 0xf1, 0x3a, 0x15, 0x8c, 0xb9, 0xbf, 0xca,
            0xbe, 0xa0, 0x18, 0xb3, 0xf8, 0xd2, 0xe5, 0x5b, 0x22, 0x81, 0xa7, 0x63, 0x62, 0x62,
            0x42, 0x73, 0x97, 0x1d, 0xba, 0xfa, 0x1e, 0x99, 0x00, 0xe8, 0x76, 0x48, 0x17, 0x00,
            0x00, 0x00, 0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x12, 0x00,
            0x40, 0x00, 0x67, 0x94, 0x7e, 0xf1, 0x3a, 0x15, 0x8c, 0xb9, 0xbf, 0xca, 0xbe, 0xa0,
            0x18, 0xb3, 0xf8, 0xd2, 0xe5, 0x5b, 0x22, 0x81, 0xa7, 0x63, 0x62, 0x62, 0x42, 0x73,
            0x97, 0x1d, 0xba, 0xfa, 0x1e, 0x99, 0x94, 0xf5, 0xf0, 0x2e, 0xe1, 0x66, 0xcd, 0x5e,
            0x14, 0xa4, 0x1e, 0x22, 0xeb, 0x6d, 0x93, 0xda, 0x79, 0xd7, 0x50, 0xda, 0x9d, 0xbc,
            0xa4, 0x84, 0xf1, 0x88, 0x3d, 0x47, 0x55, 0xbc, 0x6f, 0x5b, 0x13, 0x00, 0x6a, 0x00,
            0x67, 0x94, 0x7e, 0xf1, 0x3a, 0x15, 0x8c, 0xb9, 0xbf, 0xca, 0xbe, 0xa0, 0x18, 0xb3,
            0xf8, 0xd2, 0xe5, 0x5b, 0x22, 0x81, 0xa7, 0x63, 0x62, 0x62, 0x42, 0x73, 0x97, 0x1d,
            0xba, 0xfa, 0x1e, 0x99, 0x94, 0xf5, 0xf0, 0x2e, 0xe1, 0x66, 0xcd, 0x5e, 0x14, 0xa4,
            0x1e, 0x22, 0xeb, 0x6d, 0x93, 0xda, 0x79, 0xd7, 0x50, 0xda, 0x9d, 0xbc, 0xa4, 0x84,
            0xf1, 0x88, 0x3d, 0x47, 0x55, 0xbc, 0x6f, 0x5b, 0x04, 0x00, 0x00, 0x00, 0x54, 0x65,
            0x73, 0x74, 0x04, 0x00, 0x00, 0x00, 0x54, 0x65, 0x73, 0x74, 0x12, 0x00, 0x00, 0x00,
            0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
            0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x00,
        ];
        let mint = <Mint as MintExtensionPack>::unpack(&src).unwrap();
        assert!(mint.is_initialized);
    }
}
